package com.freight.common.wlhyupload;

import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.freight.common.core.constant.DictConstants;
import com.freight.common.core.exception.ServiceException;
import com.freight.common.core.utils.HttpUtils;
import com.freight.common.core.utils.IdUtils;
import com.freight.common.core.utils.SpringUtils;
import com.freight.common.core.utils.StringUtils;
import com.freight.common.core.utils.bean.BeanUtils;
import com.freight.common.redis.service.RedisService;
import com.freight.common.wlhyupload.constant.*;
import com.freight.common.wlhyupload.util.*;
import com.freight.common.wlhyupload.vo.*;
import com.freight.common.wlhyupload.vo.zvo.*;
import com.freight.common.wlhyupload.vo.zvo.svo.*;
import com.freight.tws.api.domain.*;
import org.apache.commons.lang3.time.DateFormatUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 网络货运上传数据(分为测试环境和正式环境)
 *  步骤：1、请求加密密码的公钥 publicKey
 *       2、利用 publicKey加密系统的登陆密码，配置好其他必需参数后请求令牌(登陆系统)，
 *          得到用于加密报文的公钥 PUBLIC_KEY和 token(两小时时效，如果有操作，会刷新过期时间，未过期不需要重复申请)
 *       3、生成一个随机的对称加密秘钥(16位uuid)将报文加密，并利用 PUBLIC_KEY加密随机秘钥，配置好其他必需参数后请求上传数据接口
 */
public final class WlhyUpload {

    /**
     * 第一步：请求加密密码的公钥
     * 1、每次请求返回的公钥都不一样，并且公钥会过期
     * 2、返回的是以 04开头的长度为 130的16进制字符串，共 1 + 64字节
     */
    private static String getPubKey() {
        try {
            String jsonString = HttpUtils.doPost(WlhyConstants.PUBKEY_PATH);
            JSONObject resultData = getResultData(jsonString);
            String publicKey = resultData.getString("publicKey");
            SpringUtils.getBean(RedisService.class).setCacheObject(WlhyConstants.PUBKEY_PWD, publicKey);
            return publicKey;
        } catch (Exception e) {
            throw new ServiceException("请求公钥失败!");
        }
    }

    /**
     * 第二步：(登录)请求令牌
     * @param account 省平台账号
     * @param pwd 省平台登录密码
     * @param is_zs 是否是正式环境  true正式  false测试
     */
    private static Map<String, String> exgLogin(String account, String pwd, boolean is_zs) {
        // 请求加密密码的公钥
        String pubKey = getPubKey();
        try {
            // 加密用户密码
            String passwd = SM2Utils.encrypt(Util.hexToByte(pubKey), pwd.getBytes());

            // 请求参数
            Map<String,String> loginParam = new HashMap<>();
            // 账号
            loginParam.put("account", account);
            // 加密后的密码
            loginParam.put("passwd", passwd);
            loginParam.put("publicRsa", pubKey);
            // 如果是正式环境此参数不用填写
            if(!is_zs) {
                loginParam.put("systemCode", "debugSystem");
            }
            String jsonString = HttpUtils.doPost(WlhyConstants.TOKEN_PATH, JSON.toJSONString(loginParam));
            JSONObject resultData = getResultData(jsonString);
            // 加密报文的公钥
            String public_key = resultData.getString(WlhyConstants.PUBKEY_KEY);
            // 两小时时效，如果有操作，会刷新过期时间，未过期不需要重复申请
            String token = resultData.getString(WlhyConstants.TOKEN);
            SpringUtils.getBean(RedisService.class).setCacheObject(WlhyConstants.PUBKEY_BW, public_key, WlhyConstants.TOKEN_TIME, TimeUnit.HOURS);
            SpringUtils.getBean(RedisService.class).setCacheObject(WlhyConstants.CACHE_TOKEN, token, WlhyConstants.TOKEN_TIME, TimeUnit.HOURS);
            Map<String, String> resultMap = new HashMap<>();
            resultMap.put(WlhyConstants.PUBKEY_KEY, public_key);
            resultMap.put(WlhyConstants.TOKEN, token);
            return resultMap;
        } catch (Exception e) {
            throw new ServiceException("(登录)请求令牌失败!");
        }
    }

    /**
     * 第三步：上传数据
     * @param ptQy 运营方（平台）信息
     * @param data  单据数据(com.freight.tws.api.domain包下的实体类对象)
     */
    public static <T> boolean wlhyUpload(PtQy ptQy, T data) {
        if(StringUtils.isNull(ptQy)) {
            throw new ServiceException("平台信息为空");
        }
        // 判断传过来的数据所在的包是否是指定的包
        if(StringUtils.isNull(data) || !WlhyConstants.PACKAGE_NAME.equals(data.getClass().getPackage().getName())) {
            throw new ServiceException("单据类型不正确!");
        }
        if(ptQy.getMonitorsState() == DictConstants.MONITORS_STATE.WKQ.getKey()) {
            throw new ServiceException("企业未开启检测，不能上传。请前往企业信息修改检测状态!");
        }
        // is_zs: true代表正式监测 false代表联调测试
        boolean is_zs = ptQy.getMonitorsState() == DictConstants.MONITORS_STATE.ZS.getKey();
        Map<String, String> map;
        // 判断token是否过期
        if(SpringUtils.getBean(RedisService.class).getCacheObject(WlhyConstants.PUBKEY_BW) == null ||
                SpringUtils.getBean(RedisService.class).getCacheObject(WlhyConstants.CACHE_TOKEN) == null) {
            // 过期重新登录
            map = exgLogin(ptQy.getAccount(), ptQy.getPasswd(), is_zs);
        } else {
            map = new HashMap<>();
            map.put(WlhyConstants.PUBKEY_KEY, SpringUtils.getBean(RedisService.class).getCacheObject(WlhyConstants.PUBKEY_BW));
            map.put(WlhyConstants.TOKEN, SpringUtils.getBean(RedisService.class).getCacheObject(WlhyConstants.CACHE_TOKEN));
        }
        try {
            // 处理报文
            String upMapStr = getDataString(ptQy, map, data);
            System.out.println("处理报文成功：");
//            System.out.println(upMapStr);
//            System.out.println("");
            // 上传
            String jsonString = HttpUtils.doPost(is_zs ? WlhyConstants.SEND_ZS_PATH : WlhyConstants.SEND_CS_PATH, upMapStr);
            System.out.println("upupup---result---upupup");
            System.out.println(jsonString);
            System.out.println("---↑↑↑↑↑↑↑---");
            JSONObject parse = JSONObject.parseObject(jsonString);
            String code = parse.getString("code");
            if("1001".equals(code)) {
                // 刷新token时间
                SpringUtils.getBean(RedisService.class).expire(WlhyConstants.PUBKEY_BW, WlhyConstants.TOKEN_TIME, TimeUnit.HOURS);
                SpringUtils.getBean(RedisService.class).expire(WlhyConstants.CACHE_TOKEN, WlhyConstants.TOKEN_TIME, TimeUnit.HOURS);
                return true;
            } else if("2001".equals(code)) {
                // token失效
                SpringUtils.getBean(RedisService.class).deleteObject(WlhyConstants.PUBKEY_BW);
                SpringUtils.getBean(RedisService.class).deleteObject(WlhyConstants.CACHE_TOKEN);
                throw new ServiceException("上传数据失败，请重试!");
            } else {
                if(StringUtils.isNotBlank(parse.getString("data"))) {
                    throw new ServiceException(parse.getString("data"));
                }
                throw new ServiceException("上传数据失败!");
            }
        } catch (Exception e) {
            throw new ServiceException("上传失败");
        }
    }

    /**
     * 处理报文
     * @param ptQy 平台信息
     * @param map 公钥和token
     * @param data  单据数据
     * @return
     */
    private static <T> String getDataString(PtQy ptQy, Map<String, String> map, T data) {
        try {
            String documentName = "";
            String ipcType = "";
            String cipherText = "";
            // 类名
            String className = data.getClass().getSimpleName();
            System.out.println("上传单据所属类名：" + className);
            switch (className) {
                // 车辆
                case "Vehicle":
                    documentName = WlhyEnums.IPC_TYPE.CL.getName();
                    ipcType = WlhyEnums.IPC_TYPE.CL.getKey();
                    Vehicle vehicle = (Vehicle) data;
                    VehicleUpVo vehicleUpVo = new VehicleUpVo();
                    BeanUtils.copyBeanProp(vehicleUpVo, vehicle);
                    // 核定载质量（千克 -> 吨）
                    if(vehicle.getVehicleTonnage() != null) {
                        BigDecimal div = NumberUtil.div(vehicle.getVehicleTonnage(), 1000, 2, RoundingMode.DOWN);
                        vehicleUpVo.setVehicleTonnage(div);
                    }
                    // 吨位（千克 -> 吨）
                    if(vehicle.getGrossMass() != null) {
                        BigDecimal div = NumberUtil.div(vehicle.getGrossMass(), 1000, 2, RoundingMode.DOWN);
                        vehicleUpVo.setGrossMass(div);
                    }
                    if(vehicle.getIssueDate() != null) {
                        vehicleUpVo.setIssueDate(Long.valueOf(formatTime(vehicle.getIssueDate(), true)));
                    }
                    if(vehicle.getRegisterDate() != null) {
                        vehicleUpVo.setRegisterDate(Long.valueOf(formatTime(vehicle.getRegisterDate(), true)));
                    }
                    cipherText = JSON.toJSONString(vehicleUpVo);
                    break;
                // 驾驶员
                case "Driver":
                    documentName = WlhyEnums.IPC_TYPE.JSY.getName();
                    ipcType = WlhyEnums.IPC_TYPE.JSY.getKey();
                    Driver driver = (Driver) data;
                    DriverUpVo driverUpVo = new DriverUpVo();
                    BeanUtils.copyBeanProp(driverUpVo, driver);
                    if(driver.getValidPeriodFrom() != null) {
                        driverUpVo.setValidPeriodFrom(Long.valueOf(formatTime(driver.getValidPeriodFrom(), true)));
                    }
                    if(driver.getValidPeriodTo() != null) {
                        driverUpVo.setValidPeriodTo(Long.valueOf(formatTime(driver.getValidPeriodTo(), true)));
                    }
                    cipherText = JSON.toJSONString(driverUpVo);
                    break;
                // 订单
                case "Order":
                    documentName = WlhyEnums.IPC_TYPE.YD.getName();
                    ipcType = WlhyEnums.IPC_TYPE.YD.getKey();
                    Order order = (Order) data;
                    OrderUpVo orderUpVo = new OrderUpVo();
                    BeanUtils.copyBeanProp(orderUpVo, order);
                    // 网络货运经营者名称
                    orderUpVo.setCarrier(ptQy.getEntName());
                    // 网络货运经营者统一社会信用代码
                    orderUpVo.setUnifiedSocialCreditIdentifier(ptQy.getUnifiedSocialCreditIdentifier());
                    // 网络货运经营者道路运输经营许可证编号
                    orderUpVo.setPermitNumber(ptQy.getPermitNumber());
                    // 运单上传时间
                    orderUpVo.setSendToProDateTime(Long.valueOf(formatTime(new Date(), false)));
                    // 运单生成时间
                    orderUpVo.setConsignmentDateTime(Long.valueOf(formatTime(order.getCreateTime(), false)));
                    // 发货时间
                    orderUpVo.setDespatchActualDateTime(Long.valueOf(formatTime(order.getDespatchActualDateTime(), false)));
                    // 收货时间
                    orderUpVo.setGoodsReceiptDateTime(Long.valueOf(formatTime(order.getGoodsReceiptDateTime(), false)));
                    // 托运人信息
                    List<ConsignorInfo> l1 = new ArrayList<>();
                    ConsignorInfo consignorInfo = new ConsignorInfo();
                    consignorInfo.setConsignor(order.getConsignor());
                    consignorInfo.setConsignorID(order.getConsignorId());
                    consignorInfo.setCountrySubdivisionCode(order.getCountrySubdivisionCode());
                    consignorInfo.setPlaceOfLoading(order.getPlaceOfLoading());
                    l1.add(consignorInfo);
                    orderUpVo.setConsignorInfo(l1);
                    // 收货方信息
                    List<ConsigneeInfo> l2 = new ArrayList<>();
                    ConsigneeInfo consigneeInfo = new ConsigneeInfo();
                    consigneeInfo.setConsignee(order.getConsignee());
                    if(StringUtils.isNotBlank(order.getConsigneeId())) {
                        consigneeInfo.setConsigneeID(order.getConsigneeId());
                    }
                    consigneeInfo.setCountrySubdivisionCode(order.getCountrySubdivisionCodeS());
                    consigneeInfo.setGoodsReceiptPlace(order.getGoodsReceiptPlace());
                    l2.add(consigneeInfo);
                    orderUpVo.setConsigneeInfo(l2);
                    // 承运人信息
                    List<ActualCarrierInfo> l3 = new ArrayList<>();
                    ActualCarrierInfo actualCarrierInfo = new ActualCarrierInfo();
                    actualCarrierInfo.setActualCarrierName(order.getActualCarrierName());
                    actualCarrierInfo.setActualCarrierBusinessLicense(order.getActualCarrierBusinessLicense());
                    actualCarrierInfo.setActualCarrierID(order.getActualCarrierId());
                    l3.add(actualCarrierInfo);
                    orderUpVo.setActualCarrierInfo(l3);
                    // 车辆信息
                    List<VehicleInfo> l4 = new ArrayList<>();
                    VehicleInfo vehicleInfo = new VehicleInfo();
                    vehicleInfo.setVehicleNumber(order.getVehicleNumber());
                    vehicleInfo.setVehiclePlateColorCode(order.getVehiclePlateColorCode());
                    // 挂车信息
                    if(StringUtils.isNotBlank(order.getTrailerVehiclePlateNumber())) {
                        vehicleInfo.setTrailerVehiclePlateNumber(order.getTrailerVehiclePlateNumber());
                    }
                    if(StringUtils.isNotBlank(order.getTrailerVehiclePlateColorCode())) {
                        vehicleInfo.setTrailerVehiclePlateColorCode(order.getTrailerVehiclePlateColorCode());
                    }
                    // 车辆信息 -> 驾驶员信息
                    List<DriverInfo> l41 = new ArrayList<>();
                    DriverInfo driverInfo = new DriverInfo();
                    driverInfo.setDriverName(order.getDriverName());
                    driverInfo.setDrivingLicense(order.getDriverLicense());
                    l41.add(driverInfo);
                    vehicleInfo.setDriver(l41);
                    // 车辆信息 -> 货物信息
                    List<OrderGoods> orderGoods = order.getOrderGoods();
                    List<GoodsInfo> l42 = new ArrayList<>();
                    for (OrderGoods orderGood : orderGoods) {
                        GoodsInfo goodsInfo = new GoodsInfo();
                        BeanUtils.copyBeanProp(goodsInfo, orderGood);
                        l42.add(goodsInfo);
                    }
                    vehicleInfo.setGoodsInfo(l42);
                    l4.add(vehicleInfo);
                    orderUpVo.setVehicleInfo(l4);
                    // 保险信息
                    List<InsuranceInformation> l5 = new ArrayList<>();
                    InsuranceInformation insuranceInformation = new InsuranceInformation();
                    insuranceInformation.setPolicyNumber(order.getPolicyNumber());
                    insuranceInformation.setInsuranceCompanyCode(order.getInsuranceCompanyCode());
                    l5.add(insuranceInformation);
                    orderUpVo.setInsuranceInformation(l5);
                    cipherText = JSON.toJSONString(orderUpVo);
                    break;
                // 资金流水单
                case "PaymentUp":
                    documentName = WlhyEnums.IPC_TYPE.ZJ.getName();
                    ipcType = WlhyEnums.IPC_TYPE.ZJ.getKey();
                    PaymentUp payment = (PaymentUp) data;
                    PaymentUpVo paymentUpVo = new PaymentUpVo();
                    BeanUtils.copyBeanProp(paymentUpVo, payment);
                    paymentUpVo.setActualCarrierID(payment.getActualCarrierId());
                    // 运单上传时间
                    paymentUpVo.setSendToProDateTime(formatTime(new Date(), false));
                    // 资金流水单 -> 运单信息
                    List<ShippingNoteList> l11 = new ArrayList<>();
                    for (Order paymentOrder : payment.getOrders()) {
                        ShippingNoteList shippingNoteList = new ShippingNoteList();
                        BeanUtils.copyBeanProp(shippingNoteList, paymentOrder);
                        l11.add(shippingNoteList);
                    }
                    paymentUpVo.setShippingNoteList(l11);
                    // 资金流水单 -> 财务信息
                    List<Financiallist> l12 = new ArrayList<>();
                    Financiallist financiallist = new Financiallist();
                    BeanUtils.copyBeanProp(financiallist, payment);
                    if(payment.getPayDate() != null) {
                        financiallist.setDateTime(Long.valueOf(formatTime(payment.getPayDate(), false)));
                    }
                    l12.add(financiallist);
                    paymentUpVo.setFinanciallist(l12);
                    cipherText = JSON.toJSONString(paymentUpVo);
                    break;
                default:
                    throw new ServiceException("单据类型不正确!!");
            }

            System.out.println(cipherText);

            //生成随机的对称加密秘钥
            String aesKey = IdUtils.fastSimpleUUID().substring(0, 16).toUpperCase();
            // 加密报文
            SM4Utils sm4 = new SM4Utils(aesKey);
            String encryptedContent = sm4.encryptData_ECB(cipherText);

            // 加密秘钥
            String encryptedCode = SM2Utils.encrypt(Util.hexToByte(map.get(WlhyConstants.PUBKEY_KEY)), aesKey.getBytes());

            Map<String, String> upMap = new HashMap<>();
            // 账号
            upMap.put("userName", ptQy.getAccount());
            // 令牌
            upMap.put("token", map.get(WlhyConstants.TOKEN));
            // 发送方代码
            upMap.put("senderCode", ptQy.getAccount());
            // 业务接口代码
            upMap.put("ipcType", ipcType);
            // 报文中文名称(电子运单、位置信息单、资金流水单、车辆信息单、驾驶员信息单)
            upMap.put("documentName", documentName);
            // 报文参考号(报文的唯一标识符，由系统按GUID规则自动产生)
            upMap.put("messageReferenceNumber", IdUtils.fastSimpleUUID().toUpperCase());
            // 报文版本号
            upMap.put("documentVersionNumber", "2.0");
            // 接收方代码
            upMap.put("recipientCode", "wlhymot");
            // 对称密钥加密串
            upMap.put("encryptedCode", encryptedCode);
            // 报文内容加密串
            upMap.put("encryptedContent", encryptedContent);
            // 报文发送的日期时间
            upMap.put("messageSendingDateTime", formatTime(new Date(), false));
            return JSON.toJSONString(upMap);
        } catch (Exception e) {
            throw new ServiceException("报文加密失败");
        }
    }

    /**
     * 解析请求结果
     */
    private static JSONObject getResultData(String resultJsonString) {
        JSONObject parse = JSONObject.parseObject(resultJsonString);
        String code = parse.getString("code");
        if("0".equals(code)) {
            return JSONObject.parseObject(parse.getString("data"));
        } else {
            throw new ServiceException("请求失败!");
        }
    }

    /**
     * 格式化时间
     */
    private static String formatTime(Date date, boolean is_ba) {
        if(is_ba) {
            return DateFormatUtils.format(date, "yyyyMMdd");
        } else {
            return DateFormatUtils.format(date, "yyyyMMddHHmmss");
        }
    }

}
